#! /usr/bin/env python
# -*- coding: utf8 -*-
"""Port of NeHe Lesson 19 by Ivan Izuver <izuver@users.sourceforge.net>

port NeHe tutorials from C\C++ to Python
"""
import sys
import random

from OpenGL.GL import *
from OpenGL.GLUT import *
from OpenGL.GLU import *
from PIL.Image import *


ESCAPE = b'\033'

# global particles settings
MAX_PARTICLES = 1000  # Number of particles create.
SPEED_PARTICLES = 2.0  # particles speed
XSPEED = 0.0  # speed OX
YSPEED = 0.0  # speed OY
ZOOM = -30.0  # zoom
LOOP = 0  # loop particles
DELAY = 0  # delay

# particle struct
class particle:
    def __init__(self):
        self.ACTIVE = 1  # dead or live
        self.LIFE = 0.0  # len of life
        self.FADE = 0.0  # step of fade

        self.R = 0.0  # red
        self.G = 0.0  # green
        self.B = 0.0  # blue

        self.X = 0.0  # X position
        self.Y = 0.0  # Y position
        self.Z = 0.0  # Z position

        self.Xi = 0.0  # X direction
        self.Yi = 0.0  # Y direction
        self.Zi = 0.0  # Z direction

        self.Xj = 0.0  # X gravity
        self.Yj = 0.0  # Y gravity
        self.Zj = 0.0  # Z gravity

# self.particle=[i for i in xrange(MAX_PARTICLES)]

prts = []

for i in range(MAX_PARTICLES):
    particl = particle()
    prts.append(particl)

Colors = [(1.0, 0.5, 0.5), (1.0, 0.75, 0.5), (1.0, 1.0, 0.5), (0.75, 1.0, 0.5),
          (0.5, 1.0, 0.5), (0.5, 1.0, 0.75), (0.5, 1.0, 1.0), (0.5, 0.75, 1.0),
          (0.5, 0.5, 1.0), (0.75, 0.5, 1.0), (1.0, 0.5, 1.0), (1.0, 0.5, 0.75)]

window = 0  # Number of the glut window.


def LoadTextures():
    #global texture
    image = open("myfire.jpg")

    ix = image.size[0]
    iy = image.size[1]
    image = image.tostring("raw", "RGBX", 0, -1)

    # Create Texture
    glBindTexture(GL_TEXTURE_2D, glGenTextures(1))  # 2d texture (x and y size)

    glPixelStorei(GL_UNPACK_ALIGNMENT, 1)
    glTexImage2D(GL_TEXTURE_2D, 0, 3, ix, iy, 0, GL_RGBA, GL_UNSIGNED_BYTE, image)
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP)
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP)
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL)


# A general OpenGL initialization function.  Sets all of the initial parameters.
def InitGL(Width, Height):  # We call this right after our OpenGL window is created.
    LoadTextures()
    global LOOP, prts, Colors
    glClearColor(0.0, 0.0, 0.0, 0.0)  # This Will Clear The Background Color To Black
    glClearDepth(1.0)  # Enables Clearing Of The Depth Buffer
    glDepthFunc(GL_LESS)  # The Type Of Depth Test To Do
    glDisable(GL_DEPTH_TEST)  # Enables Depth Testing
    glEnable(GL_BLEND)
    glShadeModel(GL_SMOOTH)  # Enables Smooth Color Shading

    glBlendFunc(GL_SRC_ALPHA, GL_ONE)
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
    glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);
    glEnable(GL_TEXTURE_2D);

    for i in range(MAX_PARTICLES):

        prts[i].LIFE = 1.0
        prts[i].FADE = float(random.randrange(0, 100)) / 1000.0 + 0.003

        idx = int(i * 12 // 1000)
        try:
            r = Colors[idx][0]
            prts[i].R = r
            prts[i].G = Colors[idx][1]
            prts[i].B = Colors[idx][2]
        except IndexError:
            print(i, idx)

        prts[i].Xi = (float(random.randrange(0, 100) % 50) - 26.0) * 10.0
        prts[i].Yi = (float(random.randrange(0, 100) % 50) - 25.0) * 10.0
        prts[i].Zi = (float(random.randrange(0, 100) % 50) - 25.0) * 10.0

        prts[i].Xj = 0.0
        prts[i].Yj = -0.8
        prts[i].Zj = 0.0

    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()  # Reset The Projection Matrix
    # Calculate The Aspect Ratio Of The Window
    gluPerspective(45.0, float(Width) / float(Height), 0.1, 200.0)

    glMatrixMode(GL_MODELVIEW)


# The function called when our window is resized (which shouldn't happen if you enable fullscreen, below)
def ReSizeGLScene(Width, Height):
    if Height == 0:  # Prevent A Divide By Zero If The Window Is Too Small
        Height = 1

    glViewport(0, 0, Width, Height)  # Reset The Current Viewport And Perspective Transformation
    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()
    gluPerspective(45.0, float(Width) / float(Height), 0.1, 200.0)
    glMatrixMode(GL_MODELVIEW)


# The main drawing function.
def DrawGLScene():
    # Clear The Screen And The Depth Buffer
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    glLoadIdentity()  # Reset The View
    global prts
    for i in range(MAX_PARTICLES):
        if prts[i].ACTIVE == 1:
            x = float(prts[i].X)
            y = float(prts[i].Y)
            z = float(prts[i].Z + ZOOM)

            glColor4f(prts[i].R == i, prts[i].G == i, prts[i].B == i, prts[i].LIFE)

            glBegin(GL_TRIANGLE_STRIP)
            glTexCoord2d(1, 1);
            glVertex3f(x + 0.5, y + 0.5, z);  # upper right
            glTexCoord2d(0, 1);
            glVertex3f(x - 0.5, y + 0.5, z);  # upper left
            glTexCoord2d(1, 0);
            glVertex3f(x + 0.5, y - 0.5, z);  # bottom right
            glTexCoord2d(0, 0);
            glVertex3f(x - 0.5, y - 0.5, z);  # bottom left
            glEnd()

            prts[i].X += prts[i].Xi / (SPEED_PARTICLES * 1000)
            prts[i].Y += prts[i].Yi / (SPEED_PARTICLES * 1000)
            prts[i].Z += prts[i].Zi / (SPEED_PARTICLES * 1000)

            prts[i].Xi += prts[i].Xj
            prts[i].Yi += prts[i].Yj
            prts[i].Zi += prts[i].Zj

            prts[i].LIFE -= prts[i].FADE
            if prts[i].LIFE < 0.0:
                prts[i].LIFE = 1.0
                prts[i].FADE = float(random.randrange(0, 100) % 100) / 1000.0 + 0.003

                prts[i].X = 0.0
                prts[i].Y = 0.0
                prts[i].Z = 0.0

                prts[i].Xi = XSPEED + float((random.randrange(0, 100) % 60) - 32.0)
                prts[i].Yi = XSPEED + float((random.randrange(0, 100) % 60) - 30.0)
                prts[i].Zi = float((random.randrange(0, 100) % 60) - 32.0)

        else:
            prts[i].ACTIVE = 1
            prts[i].FADE = float(random.randrange(0, 100)) / 1000.0 + 0.003;

            prts[i].X = 0.0
            prts[i].Y = 0.0
            prts[i].Z = 0.0

    #  since this is double buffered, swap the buffers to display what just got drawn.
    glutSwapBuffers()


# The function called whenever a key is pressed. Note the use of Python tuples to pass in: (key, x, y)
def keyPressed(key, x, y):
    global window, ZOOM, prts
    # If escape is pressed, kill everything.
    #key = str(key)
    #print(key)

    if key == ESCAPE:
        sys.exit()
    elif key in (b'W', b'w'):
        for i in range(MAX_PARTICLES):
            if prts[i].Yj < 1.5:
                prts[i].Yj += 0.1
    elif key in (b'S', b's'):
        for i in range(MAX_PARTICLES):
            if prts[i].Yj > -1.5:
                prts[i].Yj -= 0.1
    elif key in (b'A', b'a'):
        for i in range(MAX_PARTICLES):
            if prts[i].Xj > -1.5:
                prts[i].Xj -= 0.1
    elif key in (b'D', b'd'):
        for i in range(MAX_PARTICLES):
            if prts[i].Xj < 1.5:
                prts[i].Xj += 0.1
    elif key in (b'F', b'f'):
        for i in range(MAX_PARTICLES):
            prts[i].X = 0.0
            prts[i].Y = 0.0
            prts[i].Z = 0.0

            prts[i].Xi = float((random.randrange(0, 100) % 50) - 26.0) * 10.0
            prts[i].Yi = float((random.randrange(0, 100) % 50) - 25.0) * 10.0
            prts[i].Yi = float((random.randrange(0, 100) % 50) - 25.0) * 10.0
    elif key in (b'Z', b'z'):
        ZOOM += 1.0
    elif key in (b'X', b'x'):
        ZOOM -= 1.0


def main():
    global window
    # pass arguments to init
    glutInit(sys.argv)

    # Select type of Display mode:
    #  Double buffer
    #  RGBA color
    # Alpha components supported
    # Depth buffer
    glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH)

    # get a 640 x 480 window
    glutInitWindowSize(800, 600)

    # the window starts at the upper left corner of the screen
    glutInitWindowPosition(100, 100)

    # Okay, like the C version we retain the window id to use when closing, but for those of you new
    # to Python (like myself), remember this assignment would make the variable local and not global
    # if it weren't for the global declaration at the start of main.
    window = glutCreateWindow("Particles by RISC")

    # Register the drawing function with glut, BUT in Python land, at least using PyOpenGL, we need to
    # set the function pointer and invoke a function to actually register the callback, otherwise it
    # would be very much like the C version of the code.
    glutDisplayFunc(DrawGLScene)

    # Uncomment this line to get full screen.
    #glutFullScreen()

    # When we are doing nothing, redraw the scene.
    glutIdleFunc(DrawGLScene)

    # Register the function called when our window is resized.
    glutReshapeFunc(ReSizeGLScene)

    # Register the function called when the keyboard is pressed.
    glutKeyboardFunc(keyPressed)

    # Initialize our window.
    InitGL(640, 480)

    # Start Event Processing Engine
    glutMainLoop()

# Print message to console, and kick off the main to get it rolling.
if __name__ == "__main__":
    print("Hit ESC key to quit.")
    print('other commands: WASD (direction) ZX (zoom)')
    main()
